package com.ali.common;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;

public class Sm4Util {
    private static final String SM4 = "SM4";
    private static final String TRANSFORMATION_ECB = "SM4/ECB/PKCS5Padding";
    private static final String TRANSFORMATION_CBC = "SM4/CBC/PKCS5Padding";
    private static final int KEY_SIZE_128 = 128;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 获取key
     */
    public static String getKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(SM4, BouncyCastleProvider.PROVIDER_NAME);
            keyGenerator.init(KEY_SIZE_128, new SecureRandom());
            byte[] keyByte = keyGenerator.generateKey().getEncoded();
            return HexUtil.encrypt(keyByte);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * SM4加密 - ECB
     */
    public static String ecbEncrypt(String content, String key) {
        byte[] contentByte = content.getBytes(StandardCharsets.UTF_8);

        try {
            Cipher cipher = getECBCipher(key, Cipher.ENCRYPT_MODE);
            byte[] doFinalByte = cipher.doFinal(contentByte);
            return HexUtil.encrypt(doFinalByte);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取Cipher - ECB
     */
    private static Cipher getECBCipher(String keyStr, int opMode) {
        try {
            byte[] keyByte = HexUtil.getByteByDecrypt(keyStr);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB, BouncyCastleProvider.PROVIDER_NAME);
            Key key = new SecretKeySpec(keyByte, SM4);
            cipher.init(opMode, key);
            return cipher;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * SM4解密 - ECB
     */
    public static String ecbDecrypt(String encryptStr, String key) {
        try {
            byte[] encryptStrByte = HexUtil.getByteByDecrypt(encryptStr);

            Cipher cipher = getECBCipher(key, Cipher.DECRYPT_MODE);
            byte[] doFinalByte = cipher.doFinal(encryptStrByte);
            return new String(doFinalByte, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * SM4加密 - CBC
     */
    public static String cbcEncrypt(String content, String key) {
        byte[] contentByte = content.getBytes(StandardCharsets.UTF_8);

        try {
            Cipher cipher = getCBCCipher(key, Cipher.ENCRYPT_MODE);
            byte[] doFinalByte = cipher.doFinal(contentByte);
            return HexUtil.encrypt(doFinalByte);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取Cipher - CBC
     */
    private static Cipher getCBCCipher(String keyStr, int opMode) {
        try {
            byte[] keyByte = HexUtil.getByteByDecrypt(keyStr);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC, BouncyCastleProvider.PROVIDER_NAME);
            Key key = new SecretKeySpec(keyByte, SM4);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(keyByte);
            cipher.init(opMode, key, ivParameterSpec);
            return cipher;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * SM4解密 - CBC
     */
    public static String cbcDecrypt(String encryptStr, String key) {
        try {
            byte[] encryptStrByte = HexUtil.getByteByDecrypt(encryptStr);

            Cipher cipher = getCBCCipher(key, Cipher.DECRYPT_MODE);
            byte[] doFinalByte = cipher.doFinal(encryptStrByte);
            return new String(doFinalByte, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}